package simple.utils.data;


import simple.utils.bitmaps.Bitmap;
import simple.utils.bitmaps.BitmapEWAH;
import simple.utils.bitmaps.BitmapFactory;
import simple.utils.concurrent.SegmentedReadWriteLock;

import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;

/**
 * Created with IntelliJ IDEA.
 * User: rehak
 * Date: 9/30/13
 * Time: 11:06 AM
 * To change this template use File | Settings | File Templates.
 */
public class IntArray {

    private static final int ELEMENT_SIZE_BITS = 4 * 8;
    private static final int MAX_TO_STRING = 256;
    private final Bitmap bitmap;
    private final int concurrency;
    private final SegmentedReadWriteLock locks;

    public IntArray() {
        this.bitmap = createBitmap();
        this.concurrency = 0;
        this.locks = new SegmentedReadWriteLock(concurrency);
    }

    public IntArray(int concurrency) {
        this.bitmap = createBitmap();
        this.concurrency = concurrency;
        this.locks = new SegmentedReadWriteLock(concurrency);
    }

    private IntArray(int concurrency, Bitmap bitmap) {
        this.concurrency = concurrency;
        this.bitmap = bitmap;
        this.locks = new SegmentedReadWriteLock(concurrency);
    }

    public byte[] toBytes() throws IOException {
        ByteArrayOutputStream bos = new ByteArrayOutputStream();
        Bytes.writeLengthDelimited(bos, BitmapFactory.serialize(bitmap));
        bos.flush();
        bos.close();
        return bos.toByteArray();
    }

    public static IntArray fromBytes(byte[] data, int concurrency) throws IOException {
        ByteArrayInputStream in = new ByteArrayInputStream(data);
        byte[] bb = Bytes.readNext(in);
        if (bb == null) {
            throw new IOException("Not enough data!");
        }
        Bitmap bitmap = BitmapFactory.deserialize(bb);
        return new IntArray(concurrency, bitmap);
    }

    private Bitmap createBitmap() {
        return new BitmapEWAH();
    }

    public void set(int index, int val) {
        locks.lock(index, SegmentedReadWriteLock.Lock.WRITE);
        try {
            setUnsafe(index, val);
        } finally {
            locks.unlock(index, SegmentedReadWriteLock.Lock.WRITE);
        }
    }

    public void setUnsafe(int index, int val) {
        int c;
        while (val != 0) {
            c = val & (0 - val);
            int set = Integer.numberOfTrailingZeros(c);
            int setIndex = (index * ELEMENT_SIZE_BITS) + set;
            bitmap.set(setIndex);
            val = val ^ c;
        }
    }

    public int get(int index) {
        locks.lock(index, SegmentedReadWriteLock.Lock.READ);
        try {
            return getUnsafe(index);
        } finally {
            locks.unlock(index, SegmentedReadWriteLock.Lock.READ);
        }
    }

    public int getUnsafe(int index) {
        int val = 0;
        for (int i = index * ELEMENT_SIZE_BITS; i < index * ELEMENT_SIZE_BITS + ELEMENT_SIZE_BITS; i++) {
            boolean b = bitmap.get(i);
            if (b) {
                val = val | (1 << i);
            }
        }
        return val;
    }

    @Override
    public String toString() {
        StringBuilder sb = new StringBuilder();
        sb.append('[');
        int[] ar = getBitmap().toArray();
        for (int i = 0; i < ar.length && i < MAX_TO_STRING; i++) {
            int n = get(ar[i]);
            sb.append(ar[i] + "=>" + n);
            if (i < ar.length - 1 && i < MAX_TO_STRING - 1) {
                sb.append(',');
            }
        }
        sb.append(']');
        return sb.toString();

    }

    /**
     * Get bitmap of set values
     *
     * @return
     */
    public Bitmap getBitmap() {
        int[] ints = bitmap.toArray();
        // no elements
        if (ints.length == 0) {
            return new BitmapEWAH(ints);
        }
        // max index
        int max = ints[ints.length - 1] / ELEMENT_SIZE_BITS;
        int[] s = new int[max + 1]; // field with 0/1 - index in IntArray is set or not
        int total = 0; // count of total elements set
        for (int i = 0; i < ints.length; i++) {
            int index = ints[i] / ELEMENT_SIZE_BITS;
            s[index] = 1;
            total++;
        }
        int[] ar = new int[total]; // int field of length of total set elements in this IntArray
        int p = 0; // position in field
        for (int i = 0; i < s.length; i++) { // traverse all int elements
            if (s[i] == 1) {
                ar[p] = i; // if set, reflect to set array
                p++;
            }
        }
        return new BitmapEWAH(ar); // return new bitmap of set indexes
    }

    public void incrementUnsafe(int index, int incrementBy) {
        int oldVal = getUnsafe(index);
        setUnsafe(index, oldVal + incrementBy);
    }

    public void increment(int index, int incrementBy) {
        locks.lock(index, SegmentedReadWriteLock.Lock.WRITE);
        int oldVal = getUnsafe(index);
        setUnsafe(index, oldVal + incrementBy);
        locks.unlock(index, SegmentedReadWriteLock.Lock.WRITE);
    }

    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (o == null || getClass() != o.getClass()) return false;

        IntArray intArray = (IntArray) o;

        if (bitmap != null ? !bitmap.equals(intArray.bitmap) : intArray.bitmap != null) return false;

        return true;
    }

    @Override
    public int hashCode() {
        int result = bitmap != null ? bitmap.hashCode() : 0;
        return result;
    }
}
